package com.gxy.learn.securityauth.auth.filter;

import com.gxy.learn.securityauth.common.exception.BusinessException;
import com.gxy.learn.securityauth.config.JwtProperties;
import com.gxy.learn.securityauth.jwt.JwtUtil;
import com.gxy.learn.securityauth.jwt.JwtVersionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Description(接口访问资源时校验请求是否有效 判断账号是否有效等)
 * AuthenticationException（认证异常）
 *      AuthenticationException:认证异常的父类，抽象类
 *      BadCredentialsException:登录凭证（密码）异常
 *      InsufficientAuthenticationException:登陆凭证不够充分而抛出的异常
 *      SessionAuthenticationException:会话并发管理时抛出的异常，例如会话总数超出最大限制数
 *      UsernameNotFoundException:用户名不存在异常
 *      PreAuthenticatedCredentialsNotFoundException:身份预认证失败异常
 *      ProviderNotFoundException:未配置AuthenticationProvider异常
 *      AuthenticationServiceException:由于系统问题而无法处理认证请求异常
 *      InternalAuthenticationServiceException:由于系统问题而无法处理认证请求异常，和AuthenticationServiceException不同之处在于如果外部系统出错，不会抛出该异常
 *      AuthenticationCredentialsNotFoundException:SecuityContext 中不存在认证主体时抛出的异常
 *      NonceExpiredException:HTTP摘要认证时随机数过期异常
 *      RememberMeAuthenticationException:RememberMe认证异常
 *      CookieTheftException :RememberMe认证时Cookie被盗窃异常
 *      InvalidCookieException:RememberMe认证时无效的Cookie异常
 *      AccountStatusException:账户状态异常
 *      LockedException:账户被锁定异常
 *      DisabledException:账户被禁用异常
 *      CredentialsExpiredException:登录凭证（密码）过期异常
 *      AccountExpiredException:账户过期异常
 *
 * AccessDeniedException(权限异常)
 *      AccessDeniedException ：权限异常的父类
 *      AuthorizationServiceException: 由于系统问题而无法处理权限时抛出异常
 *      CsrfException:Csrf令牌异常
 *      MissingCsrfTokenException:Csrf令牌缺失异常
 *      InvalidCsrfTokenException:Csrf令牌无效异常
 * @author: Gao xueyong
 * Create at: 2021/12/1 下午10:11
 */
@Slf4j
@Component
public class SecurityAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    private UserDetailsService userDetailsService;

    @Autowired
    private HandlerExceptionResolver handlerExceptionResolver;

    @Autowired
    private JwtVersionUtil jwtVersionUtil;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        log.info(" request url: {}", request.getRequestURL());
        String authToken = request.getHeader(JwtProperties.getHeader());
        log.info("进入自定义过滤器");
        if (request.getMethod().equals("OPTIONS")) {
            response.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,HEAD");
            response.setHeader("Access-Control-Allow-Headers", response.getHeader("Access-Control-Request-Headers"));
            filterChain.doFilter(request, response);
        }
        SecurityContext securityContext = SecurityContextHolder.getContext();
        if (StringUtils.isBlank(authToken)) {
            handlerExceptionResolver.resolveException(request, response, null, new BadCredentialsException("Authorization为空！"));
            return;

        }
        if (null == securityContext) {
            handlerExceptionResolver.resolveException(request, response, null, new BusinessException("错误请求，请重新登录"));
            return;

        }
        String loginUserName = JwtUtil.getUsername(authToken);
        if (StringUtils.isBlank(loginUserName)) {
            handlerExceptionResolver.resolveException(request, response, null, new UsernameNotFoundException("Authorization错误，请重新登录"));
            return;
        }
        if (!JwtUtil.verify(authToken)) {
            handlerExceptionResolver.resolveException(request, response, null, new BadCredentialsException("Authorization错误，请重新登录"));
            return;
        }

        Integer cacheVersion = jwtVersionUtil.loginVersion(loginUserName);
        Integer loginVersion = JwtUtil.getLoginVersion(authToken);
        if (null == cacheVersion || !cacheVersion.equals(loginVersion)) {
            handlerExceptionResolver.resolveException(request, response, null, new CredentialsExpiredException("Authorization错误，请重新登录！"));
            return;
        }

        /**
         * UsernamePasswordAuthenticationToken继承AbstractAuthenticationToken实现Authentication
         * 所以当在页面中输入用户名和密码之后首先会进入到UsernamePasswordAuthenticationToken验证(Authentication)，
         * 然后生成的Authentication会被交由AuthenticationManager来进行管理
         * 而AuthenticationManager管理一系列的AuthenticationProvider，
         * 而每一个Provider都会通UserDetailsService和UserDetail来返回一个
         * 以UsernamePasswordAuthenticationToken实现的带用户名和密码以及权限的Authentication
         */
        UserDetails userDetails = this.userDetailsService.loadUserByUsername(loginUserName);

        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());

        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        //将authentication放入SecurityContextHolder中
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }
}
